<?php
/*********************************************
  CPG DragonflyCMS, Copyright (c) 2011 by DragonflyCMS Dev Team
  http://dragonflycms.org

  Dragonfly is released under the terms and conditions
  of the GNU GPL version 2 or any later version
**********************************************/

/**
 DFSubject as SplSubject
 Notifies observers
**/
interface DFSubject
{
	# Get a subject to include an observer
	public function attach($observer);
	# Get a subject to exclude an observer
	public function detach($observer);
	public function notify($subject=__CLASS__);
}

/**
 DFService as SplObserver
 Be notified by subjects
**/
interface DFService
{
	# Let the Subject knows your priority
	# 1 highest, 99 lowest. 1-10 and 90-99 are reserved.
	public function priority();
	public function runlevel();
	public function update();
	//public function initAs();
	//public function addSubject($subject);
}

class DF implements DFSubject
{

	const
		BOOT_DOWN   = 0, // register_shutdown_function
		BOOT_CORE   = 1, // filters-php-debugger-error-loader
		BOOT_BASE   = 2, // cmsinit.inc-db-session-maincfg-url-language-hooks
		BOOT_ETH    = 3, // client-security-networking-hooks
		BOOT_CUSTOM = 4, // N/A
		BOOT_USER   = 5, // tpl-output-module-blocks-hooks
		BOOT_ERROR  = 6, // hooks-error-reboot(redirect)
		BOOT_INSTALL  = 7,
		BOOT_HEADER = 8;


	# the current working state (runlevel)
	private $current_state;

	# services container
	# group by runlevel
	# execute by priority
	private $services=array(
		DF::BOOT_DOWN   => array(),
		DF::BOOT_CORE   => array(),
		DF::BOOT_BASE   => array(),
		DF::BOOT_ETH    => array(),
		DF::BOOT_CUSTOM => array(),
		DF::BOOT_USER   => array(),
		DF::BOOT_ERROR  => array(),
		DF::BOOT_INSTALL => array()
	);

	public function runlevel() { return DF::BOOT_CORE; }

	public function setState($state)
	{
		$this->current_state = $state;
		$this->notify();
	}

	public function getState()
	{
		return $this->current_state;
	}

	public function attach($observer, $Object=null)
	{
		$toUpper = strtoupper($observer);
		if (class_exists('Dragonfly', false) && !($Object = Dragonfly::getKernel()->$toUpper)) {
			trigger_error("Use Dragonfly::getKernel()->extend() to attach.", E_USER_WARNING);
			return;
		} else if (!isset($Object) || !is_object($Object)) {
			trigger_error("Must be Objects.", E_USER_WARNING);
			return;
		}

		//$GLOBALS[$object]->addSubject = __CLASS__;
		$hash = spl_object_hash($Object);
		$priority = $Object->priority();
		$runlevel = $Object->runlevel();
		$runlevel = is_array($runlevel) ? $runlevel : array($runlevel);
		foreach ($runlevel as $i) {
			if (!isset($this->services[$i][$priority][$hash])) {
				$this->services[$i][$priority][$hash] = $observer;
				if ($i === $this->current_state) $Object->update();
		}}
		return true;
	}

	public function detach($observer)
	{
		$toUpper = strtoupper($observer);
		if (isset($GLOBALS[$observer])) $Object = $GLOBALS[$observer];
		else if (!($Object = Dragonfly::getKernel()->$toUpper)) {
			trigger_error("Could not found $observer.", E_USER_WARNING);
			return;
		}

		//$GLOBALS[$observer]->removeSubject = __CLASS__;
		$hash = spl_object_hash($Object);
		$priority = $Object->priority();
		$runlevel = $Object->runlevel();
		$runlevel = is_array($runlevel) ? $runlevel : array($runlevel);
		foreach ($runlevel as $i) {
			if (isset($this->services[$i][$priority][$hash])) {
				$Object->update();
				unset($this->services[$i][$priority][$hash]);
		}}
	}

	public function detachAll()
	{
		switch ($this->current_state)
		{
			case DF::BOOT_DOWN: break;
			default: $this->setState(DF::BOOT_ERROR);
		}
	}

	# DF will only notify about runlevel state changes
	public function notify($subject=__CLASS__)
	{
		$i=$this->current_state;
		$notified = array();
		if (empty($this->services[$i])) return;
		# notify from higher to lower priority ( 0 -> 100 )
		ksort($this->services[$i]);
		//$pdata['subject'] = $subject;
		//$pdata['state'] = $this->current_state;
		foreach ($this->services[$i] as $priority => $hash__class)
		{
			$hash = key($hash__class);
			$class = $hash__class[$hash];
			$toUpper = strtoupper($class);

			if (isset($GLOBALS[$class])) {
				$Object = $GLOBALS[$class];
			}
			else if (!($Object = Dragonfly::getKernel()->$toUpper)) {
				trigger_error("Could not found $observer.", E_USER_WARNING);
				continue;
			}

			# notify only once for the current runlevel
			if (!isset($notified[$hash])) {
				$notified[$hash] = $class;
				# when update() returns true propagation will be halted for the current level
				$Object->update();
			}
		}
	}

	# autoload() ads a few milliseconds on each call
	public function autoLoad($class_name)
	{
		$class_name = ltrim($class_name,'\\');
		# avoids endless loops and resultant inproperly closed classes
//		if (DF::BOOT_DOWN === $this->current_state) { return; }
/*
		if (!preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#', $class_name)) {
			# PEAR bug and such
			return;
		}
*/
		# split class_name into segments where the
		# first segment is the library or component
		if (strpos($class_name, '\\')) {
			$path = explode('\\', $class_name);
			$path = array_merge(array_slice($path,0,-1), explode('_', array_pop($path)));
		} else {
			$path = explode('_', $class_name);
		}
		if (!$path) return;

		if (empty($path[1])) {
			// Try Dragonfly legacy class
			$file = CLASS_PATH.strtolower($class_name).'.php';
			if (is_file($file)) {
				include_once($file);
				return;
			}
		}

		/** Case-sensitive match */
		$file = implode('/',$path).'.php';
		if ($file = Poodle::getFile($file)) {
			include_once($file);
			return;
		}

		/** When the class name is the directory itself add itself as filename  */
		if (!isset($path[2])) { $path[2] = $path[1]; }

		/** Default spl_autoload_register also lowercases the filename  */
		$path = array_map('strtolower', $path);

		/** Temporary disable "failed to open stream: No such file or directory" warning */
		$Debugger = $GLOBALS['Debugger'];
		$error_level = $Debugger->setErrorLevel($Debugger->error_level & ~E_WARNING);
		$log_level = $Debugger->setLogLevel($Debugger->log_level & ~E_WARNING);

		include_once(implode('/',$path).'.php');

		if (!class_exists($class_name, false) && !interface_exists($class_name, false))
		{
			/** Last attempt to find class in global container directory */
			$lib = $path[0];
			array_shift($path);
			if ($path[0] === $path[1]) { array_shift($path); }
			$file = implode('_',$path);
			if ($interface = in_array('interface', $path)) {
				$file = $lib.'/interfaces/'.str_replace('_interface','',$file);
			} else {
				$file = $lib.'/classes/'.$file;
			}
			include_once($file.'.php');
			$Debugger->setErrorLevel($error_level);
			$Debugger->setLogLevel($log_level);
			if (stripos($class_name,'interface') && !interface_exists($class_name, false)) {
				if (class_exists('Poodle\\PHP\\INI', false)) \Poodle\PHP\INI::set('display_errors', 1);
				throw new Exception("Interface {$class_name} not found");
			}
			if (!class_exists($class_name, false)) {
				// Try Dragonfly legacy class
				$file = CLASS_PATH.strtolower($class_name).'.php';
				if (is_file($file)) {
					include_once($file);
					if (class_exists($class_name, false)) { return; }
				}
				if (class_exists('Poodle\\PHP\\INI', false)) \Poodle\PHP\INI::set('display_errors', 1);
				throw new Exception("Class {$class_name} not found");
			}
		}
		$Debugger->setErrorLevel($error_level);
		$Debugger->setLogLevel($log_level);
	}
}

class PerformanceLog implements DFService
{

	private static $data = array();
	private static $next = 0;
	private static $partial_time = 0;
	private static $partial_memory = 0;

	public static function start()
	{
		self::$data[0]['file'] = str_replace(BASEDIR, '', __FILE__);
		self::$data[0]['line'] = __LINE__;
		self::$data[0]['time'] = 0;
		self::$data[0]['memory'] = 0;
		++self::$next;
	}

	public static function stop()
	{
		if (1 === self::$next) return '';
		$o = 'Performance Log: <br />';
		//array_shift(self::$data);
		while ($data = array_shift(self::$data)) {
			$f = $data['file']; $l = $data['line'];
			$t = round($data['time']*1000,1);
			$m = round((round($data['memory'] / 1048576 * 100) / 100), 2);
			$o.= sprintf('%1$s at line %2$s: in %3$s ms, with %4$s MB<br />', $f, $l, $t, $m);
			$data = $f = $l = $t = $m = null;
		}
		$o .= 'with a peak of '. round((round(memory_get_peak_usage() / 1048576 * 100) / 100), 2). 'MB';
		return $o;
	}

	public static function splitTime()
	{
		$backtrace = debug_backtrace(false);
		while ($callee = array_pop($backtrace)) {
			if ((isset($callee['class']) && 'DF' === $callee['class']) || __FUNCTION__ === $callee['function']) break;
		}
		$t = microtime(true) - START_TIME - self::$partial_time;
		$m = memory_get_usage() - START_MEMORY_USAGE - self::$partial_memory;
		self::$data[self::$next] = array(
			'file' => str_replace(BASEDIR, '', $callee['file']),
			'line' => $callee['line'],
			'time' => $t,
			'memory' => $m
		);
		self::$partial_time += $t;
		self::$partial_memory += $m;
		++self::$next;
	}

	public function name()     { return __CLASS__ ; }
	public function priority() { return 89; }
	public function runlevel() { return array(DF::BOOT_CORE, DF::BOOT_BASE, DF::BOOT_ETH, DF::BOOT_USER); }
	public function update() {
		global $DF;
		switch ($DF->getState()) {
			case DF::BOOT_CORE: return self::start();
			//case DF::BOOT_DOWN: case DF::BOOT_ERROR: return self::stop();
			case DF::BOOT_BASE: case DF::BOOT_ETH: return self::splitTime();
			default: self::splitTime();
		}
		return;
	}

}

function Prepare_INPUT(&$value, $key) {
    if (is_array($value)) { array_walk($value, 'Prepare_INPUT', false); }
    else {
        $value = preg_replace('#\p{Zs}#u', ' ', $value);
    }
}
